#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
//#include <search.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/keyvalq_struct.h>


#define MENU_TITLE_B	"menuentry '"
#define MENU_TITLE_E	"'"
/*
#define SUBMENU_TITLE_B	"submenu '"
#define SUBMENU_TITLE_E	"'"
*/
#define MENU_BODY_B 	"{"
#define MENU_BODY_E 	"}"
#define MAX_RGRUB_REQ	128
#define MAX_MENU_ENTRY	32
#define MAX_TITLE_LEN 	256

#define MAX_CFGLINE_LEN		64
#define	MAX_CFG_ITEM_NUM	1024

typedef struct {
    char client_name[MAX_CFGLINE_LEN];
    char menu_id[MAX_CFGLINE_LEN];
} rgrub_cfg;

rgrub_cfg	g_cfg[MAX_CFG_ITEM_NUM];

typedef struct {
    char title[MAX_TITLE_LEN];
} menu_entry;

typedef struct {
    char	mac[MAX_CFGLINE_LEN];
    /*
    char	*conf;
    unsigned int	conf_len;
    */
    menu_entry		entry[MAX_MENU_ENTRY];
    unsigned int	entry_num;
    int				selected;
    struct evhttp_request	*req;
} rgrub_req;

static rgrub_req	g_req[MAX_RGRUB_REQ];

static rgrub_req*
alloc_rgrub_req()
{
    for(int i = 0; i < MAX_RGRUB_REQ; i++)
        if(g_req[i].mac[0] == 0)
            return &g_req[i];
    return NULL;
}

static void
init_rgrub_req(unsigned int id)
{
    if (id >= MAX_RGRUB_REQ)
        return;
    g_req[id].mac[0] = 0;
    g_req[id].req = NULL;
    g_req[id].entry_num = 0;
    g_req[id].selected = -1;
}

static void
free_rgrub_req(unsigned int id)
{
    if (id >= MAX_RGRUB_REQ)
        return;
    g_req[id].mac[0] = 0;
    //evhttp_request_free(g_req[id].req);
    g_req[id].req = NULL;
    g_req[id].entry_num = 0;
    g_req[id].selected = -1;
}

static void
init_rgrub()
{
    for(int i = 0; i < MAX_RGRUB_REQ; i++)
        init_rgrub_req(i);
}

static void
dealwith_conflict(const char *id)
{
    if(id == NULL)
        return;
    for (int i = 0; i < MAX_RGRUB_REQ; i++)
    {
        if(strcmp(id, g_req[i].mac) == 0)
            free_rgrub_req(i);
    }
    return;
}

static void
rgrub_element_cb(struct evhttp_request *req, void *arg)
{
    struct evbuffer	*buf = evbuffer_new();
    int fd = -1;
    struct stat st;
    struct evkeyvalq	*out_headers;

    out_headers = evhttp_request_get_output_headers(req);
    evhttp_add_header(out_headers, "Content-Type", "text/css; charset=UTF-8");

    if ((fd = open("../web/w3.css", O_RDONLY)) < 0)
    {
        perror("open");
        goto err;
    }
    if (fstat(fd, &st)<0) {
        /* Make sure the length still matches, now that we
         * opened the file :/ */
        perror("fstat");
        goto err;
    }
    evbuffer_add_file(buf, fd, 0, st.st_size);
    evhttp_send_reply(req, 200, "OK", buf);
    goto done;
err:
    evhttp_send_reply(req, 200, "OK", NULL);
    if(fd >= 0)
        close(fd);
done:
    if(buf)
        evbuffer_free(buf);
}

static void
rgrub_ok(struct evhttp_request *req, void *arg)
{
    evhttp_send_reply(req, 200, "OK", NULL);
}

static void
rgrub_set_cb(struct evhttp_request *req, void *arg)
{
    struct evbuffer	*buf = evbuffer_new();
    struct evkeyvalq *headers = NULL;
    struct evkeyvalq *out_headers = NULL;
    int fd;
    struct stat st;

    out_headers = evhttp_request_get_output_headers(req);
    headers = evhttp_request_get_input_headers(req);
    const char* value = evhttp_find_header(headers, "Get-Client");
    if(value && strcmp(value, "client_list") == 0)
    {
        char json[1024*256];
        json[0] = 0;
        strcat(json, "{ \"client_list\":[");
        for(int i = 0; i < MAX_RGRUB_REQ; i++)
        {
            if(g_req[i].entry_num == 0)
            {
                free_rgrub_req(i);
                continue;
            }
            if(g_req[i].mac[0] != 0)
            {
                strcat(json, " {");
                strcat(json, "\"name\":");
                strcat(json, "\"");
                strcat(json, g_req[i].mac);
                strcat(json, "\"");
                strcat(json, "}, ");
            }
        }
        //rm ,
        int fix = strlen(json);
        if(fix > 2)
        {
            fix -= 2;
            json[fix] = 0;
            strcat(json, "] }");
        }
        //
        //printf("%s\n", json);
        evbuffer_add(buf, json, strlen(json));
        evhttp_add_header(out_headers, "Content-Type", "application/json; charset=UTF-8");
        evhttp_send_reply(req, 200, "OK", buf);
        return;
    }
    else if (value)
    {
        char json[1024*256];
        json[0] = 0;
        //struct evbuffer *inbuf = evhttp_request_get_input_buffer(req);

        strcat(json, "{ \"menuentry\":[");
        for(int i = 0; i < MAX_RGRUB_REQ; i++)
        {
            if(strcmp(g_req[i].mac, value))
                continue;
            for(int j = 0; j < g_req[i].entry_num; j++)
            {
                strcat(json, " {");
                strcat(json, "\"title\":");
                strcat(json, "\"");
                strcat(json, g_req[i].entry[j].title);
                strcat(json, "\"");
                strcat(json, "}, ");
            }
        }
        //rm ,
        int fix = strlen(json);
        if(fix > 2)
        {
            fix -= 2;
            json[fix] = 0;
            strcat(json, "] }");
        }
        //
        //printf("%s\n", json);
        evbuffer_add(buf, json, strlen(json));
        evhttp_add_header(out_headers, "Content-Type", "application/json; charset=UTF-8");
        evhttp_send_reply(req, 200, "OK", buf);
        return;
    }

    value = evhttp_find_header(headers, "Grub-Set");
    if(value)
    {
        const char* name = evhttp_find_header(headers, "Client-Name");
        for(int i = 0; i < MAX_RGRUB_REQ; i++)
        {
            if(strcmp(g_req[i].mac, name))
                continue;
            evbuffer_add(buf, value, strlen(value));
            evhttp_send_reply(g_req[i].req, 200, "OK", buf);
            free_rgrub_req(i);
        }
        evhttp_send_reply(req, 200, "OK", NULL);
        return;
    }

    if ((fd = open("../web/rgrub_set.html", O_RDONLY)) < 0)
    {
        perror("open");
        goto err;
    }
    if (fstat(fd, &st)<0) {
        /* Make sure the length still matches, now that we
         * opened the file :/ */
        perror("fstat");
        goto err;
    }
    evbuffer_add_file(buf, fd, 0, st.st_size);

    evhttp_add_header(out_headers, "Content-Type", "text/html; charset=UTF-8");
    evhttp_send_reply(req, 200, "OK", buf);
    goto done;
err:
    evhttp_send_error(req, 404, "doc not found");
    if(fd >= 0)
        close(fd);
done:
    if(buf)
        evbuffer_free(buf);
}

static void
rgrub_request_cb(struct evhttp_request *req, void *arg)
{
    //const char *cmdtype;
    struct evkeyvalq *headers;
    //struct evkeyval *header;
    struct evbuffer *buf = NULL;
    struct evbuffer *databuf = evbuffer_new();

    /*
    switch (evhttp_request_get_command(req)) {
    case EVHTTP_REQ_GET: cmdtype = "GET"; break;
    case EVHTTP_REQ_POST: cmdtype = "POST"; break;
    case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
    case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
    case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
    case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
    case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
    case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
    case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
    default: cmdtype = "unknown"; break;
    }
    */

    /*
    const char* hey = NULL;
    const struct evhttp_uri	*my_url = evhttp_request_get_evhttp_uri(req);
    hey = evhttp_uri_get_query(my_url);
    */

    /*
    printf("Received a %s request for %s\nHeaders:\n",
        cmdtype, evhttp_request_get_uri(req));
    */

    buf = evhttp_request_get_input_buffer(req);
    if(evbuffer_get_length(buf) == 0)
        goto err;

    headers = evhttp_request_get_input_headers(req);
    const char* mac = evhttp_find_header(headers, "RGrub");
    if(mac && strcmp(mac, "") != 0)
    {
        for(int i = 0; i < MAX_CFG_ITEM_NUM; i++)
        {
            //no more
            if(g_cfg[i].client_name[0] == 0)
                break;
            //find
            if(strcmp(g_cfg[i].client_name, mac) == 0)
            {
                evbuffer_add(databuf, g_cfg[i].menu_id, strlen(g_cfg[i].menu_id));
                evhttp_send_reply(req, 200, "OK", databuf);
                evbuffer_free(databuf);
                return;
            }
        }
    }
    if(mac && strcmp(mac, "") != 0)
    {
        dealwith_conflict(mac);
        rgrub_req	*rgrub = alloc_rgrub_req();
        if(rgrub == NULL)
            goto err;
        strncpy(rgrub->mac, mac, MAX_CFGLINE_LEN);
        /*
        struct evbuffer_ptr	p_begin;
        struct evbuffer_ptr	p_end;
        evbuffer_ptr_set(buf, &p_begin, 0, EVBUFFER_PTR_SET);
        evbuffer_ptr_set(buf, &p_end, 0, EVBUFFER_PTR_SET);
        while (1) {
            p_begin = evbuffer_search(buf, MENU_TITLE_B, strlen(MENU_TITLE_B), &p_begin);
            if(p_begin.pos < 0)
                break;
            evbuffer_ptr_set(buf, &p_end, strlen(MENU_TITLE_B), &p);
            p = evbuffer_search(buf, MENU_TITLE_E, strlen(MENU_TITLE_E), &p);
            if(p.pos < 0)
                break;
        }
        */
        //evbuffer_search(buf, MENU_TITLE_B, ^)

        //char conf[1024 * 1024];
        char	*entry = NULL;
        while (1) {
            entry = evbuffer_readln(buf, NULL, EVBUFFER_EOL_ANY);
            if(entry == NULL)
                break;
            strncpy(rgrub->entry[rgrub->entry_num].title, entry, MAX_TITLE_LEN);
            rgrub->entry_num++;
            rgrub->req = req;
            if(rgrub->entry_num == MAX_MENU_ENTRY)
                break;
        }
        /*
        if(rgrub->entry_num == 0)
            free_rgrub_req();
            */
        goto done;
    }

    /*
    for (header = headers->tqh_first; header;
        header = header->next.tqe_next) {
        printf("  %s: %s\n", header->key, header->value);
    }
    */

    /*
    puts("Input data: <<<");
    //evbuffer
    while (evbuffer_get_length(buf)) {
        int n;
        char cbuf[128];
        n = evbuffer_remove(buf, cbuf, sizeof(cbuf));
        if (n > 0)
            (void) fwrite(cbuf, 1, n, stdout);
    }
    puts(">>>");
    */

    /*
    if(para_i && 0 == strcmp(para_i, "sleep"))
        //sleep(100);
        return;
        */

err:
    evhttp_send_reply(req, 200, "OK", NULL);
done:
    //evbuffer_add(databuf, "0", 1);
    //evhttp_send_reply(req, 200, "OK", NULL);
    //evhttp_send_reply(req, 200, "OK", databuf);
    //evbuffer_free(databuf);
    return;
}

int
init_conf(const char *conf_path)
{
    FILE	*fp;
    int		ret = 0;
    char	tmp[MAX_CFG_ITEM_NUM + 1];

    for(int i = 0; i < MAX_CFG_ITEM_NUM; i++)
        g_cfg[i].client_name[0] = 0;
    fp = fopen(conf_path, "r");
    if(fp == NULL)
    {
        fprintf(stderr, "can not open file %s", conf_path);
        return -1;
    }
    for(int i = 0; i < MAX_CFG_ITEM_NUM; i++)
    {
        if( NULL == fgets(tmp, MAX_CFGLINE_LEN, fp))
            break;
        if( NULL == strchr(tmp, '\n'))
        {
            fprintf(stderr, "invalid config: too long %s...\r\n", tmp);
            ret = -1;
            goto end;
        }
        sscanf(tmp, "%s %s", g_cfg[i].client_name, g_cfg[i].menu_id);
        if( strlen(g_cfg[i].client_name) == 0 || strlen(g_cfg[i].menu_id) == 0)
        {
            fprintf(stderr, "invalid config: %s\r\n", tmp);
            ret = -1;
            goto end;
        }
        /*
        const char *kg = strchr(tmp, ' ');

        if(NULL == kg)
        {
            fprintf(stderr, "invalid config: %s\r\n", tmp);
            ret = -1;
            goto end;
        }
        strncpy(g_cfg[i].client_name, tmp, kg - tmp);
        */
        //printf("%s %s\r\n", g_cfg[i].client_name, g_cfg[i].menu_id);
    }

end:
    fclose(fp);
    return ret;
}

int
main(int argc, char *argv[])
{
    struct event_base *base;
    struct evhttp *http;
    struct evhttp_bound_socket *handle;
    ev_uint16_t port = 0;

    if (argc < 3) {
        fprintf(stderr, "syntax: rgrub port config\n");
        return 1;
    }
    port = atoi(argv[1]);

    init_rgrub();

    if(0 != init_conf(argv[2]))
        return 1;

    base = event_base_new();
    if (!base) {
        fprintf(stderr, "couldn't create an event_base: exiting\n");
        return 1;
    }

    http = evhttp_new(base);
    if (!http) {
        fprintf(stderr, "couldn't create evhttp: exiting.\n");
        return 1;
    }

    evhttp_set_cb(http, "/rgrub", rgrub_request_cb, NULL);
    evhttp_set_cb(http, "/rgrub_set", rgrub_set_cb, NULL);
    evhttp_set_cb(http, "/element/w3.css", rgrub_element_cb, NULL);

    /* Now we tell the evhttp what port to listen on */
    handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", port);
    if (!handle) {
        fprintf(stderr, "couldn't bind to port %d. exiting.\n",
            (int)port);
        return 1;
    }

    event_base_dispatch(base);
    return 0;
}

